package com.partner.boot.service.impl;

import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.partner.boot.common.Constants;
import com.partner.boot.common.enums.EmailCodeEnum;
import com.partner.boot.controller.domain.LoginDTO;
import com.partner.boot.controller.domain.UserRequest;
import com.partner.boot.entity.Permission;
import com.partner.boot.entity.Role;
import com.partner.boot.entity.RolePermission;
import com.partner.boot.entity.User;
import com.partner.boot.exception.ServiceException;
import com.partner.boot.mapper.RolePermissionMapper;
import com.partner.boot.mapper.UserMapper;
import com.partner.boot.service.IPermissionService;
import com.partner.boot.service.IRoleService;
import com.partner.boot.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.partner.boot.utils.EmailUtils;
import com.partner.boot.utils.RedisUtils;
import com.partner.boot.utils.SpringContextUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 小熊
 * @since 2023-02-13
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    private static final long TIME_IN_MS5 = 5 * 60 * 1000;  // 表示5分钟的毫秒数

    @Autowired
    EmailUtils emailUtils;
    @Resource
    RolePermissionMapper rolePermissionMapper;
    @Resource
    IRoleService roleService;
    @Resource
    IPermissionService permissionService;

    @Override
    public LoginDTO login(UserRequest user) {

        User dbUser;

        try {
            dbUser = getOne(new UpdateWrapper<User>().eq("username", user.getUsername())
                    .or().eq("email",user.getUsername()));
        } catch (Exception e) {
            throw new RuntimeException("数据库异常");
        }

        if(dbUser == null){
            throw new ServiceException("用户不存在");
        }

        if (!BCrypt.checkpw(user.getPassword(), dbUser.getPassword())) { // 明文 数据库中的加密密码
            throw new ServiceException("用户名或密码错误");
        }

        StpUtil.login(dbUser.getUid()); // 把权限信息存入到应用里去 ; StpInterfaceImpl 里的 loginId 来源;
        StpUtil.getSession().set(Constants.LOGIN_USER_KEY,dbUser);
        String tokenValue = StpUtil.getTokenInfo().getTokenValue(); // 获取 Token  相关参数

        // 先拿到角色，再通过角色查询权限
        // 查询用户菜单树 树只有两层
        String flag = dbUser.getRole();
        // 获取角色对应的菜单树
        List<Permission> all = getPermissions(flag);        // 水平
        List<Permission> menus = getTreePermissions(all);   // 树

        // 权限分三种 目录 页面 按钮，按钮需要单独拿出来，因为在页面会通过v-if控制按钮的显示和隐藏，所以需要给这个页面一个单独的集合
        // 页面的按钮权限集合
        List<Permission> auths = all.stream().filter(permission -> permission.getType() == 3).collect(Collectors.toList());

        return LoginDTO.builder().user(dbUser).token(tokenValue).menus(menus).auths(auths).build();

    }

    // 获取角色对应的菜单树 水平
    public List<Permission> getPermissions(String roleFlag) {
        //  1 获取 role 角色 唯一标识 flag。查询用户菜单树 树只有两层
        Role role = roleService.getOne(new QueryWrapper<Role>().eq("flag",roleFlag));
        // 2 通过这个List可以得到 角色的 一组permission的id
        List<RolePermission> rolePermissions = rolePermissionMapper.selectList(new QueryWrapper<RolePermission>().eq("role_id", role.getId()));
        // 3  然后得到 角色的 permissio id 数组，即权限数组。 同一个角色 它的所以权限只有唯一一个 所有可以得到唯一的数组。
        List<Integer> permissionIds = rolePermissions.stream().map(RolePermission::getPermissionId).collect(Collectors.toList());
        // 4 查询出 所有的 权限权限集合
        List<Permission> permissionList = permissionService.list();
        // 5 当前用户权限对象集合  水平的菜单树；区别于树；
        List<Permission> all = new ArrayList<>();
        // 6 从权限集合里面 得到每一个权限对象
        for (Integer permissionId : permissionIds) {
            // 从权限集合里面将角色具有的权限对象加到all列表里去； ifPresent 如果值不为空
            permissionList.stream().filter(permission -> permission.getId().equals(permissionId)).findFirst()
                    .ifPresent(all::add);
        }
        return all;
    }


    // 获取角色对应的菜单树
    private List<Permission> getTreePermissions(List<Permission> all) {
        // 菜单树 1级 -> 2级
        // type==1 是目录  或者  pid = null 没有父级肯定就是第一级
        List<Permission> parentList = all.stream().filter(permission -> permission.getType() == 1
                || (permission.getType() == 2 && permission.getPid() == null)).collect(Collectors.toList());
        for (Permission permission : parentList) {
            // 子集的Pid
            Integer pid = permission.getId();
            // 2级菜单
            List<Permission> level2List = all.stream().filter(permission1 -> pid.equals(permission1.getPid())).collect(Collectors.toList());
            permission.setChildren(level2List);
        }
        // 排序
        return parentList.stream().sorted(Comparator.comparing(Permission::getOrders)).collect(Collectors.toList());
    }

    @Override
    public void register(UserRequest user) {

        // 校验邮箱
        String key = Constants.EMAIL_CODE + EmailCodeEnum.REGISTER.getValue() + user.getEmail();
        validateEmail(key,user.getEmailCode()); // user.getEmail()

        User dbUser;
        try {
            dbUser = getOne(new UpdateWrapper<User>().eq("username", user.getUsername()));
        } catch (Exception e) {
            throw new RuntimeException("数据库异常");
        }

        if(dbUser != null){
            throw new ServiceException("用户名已存在");
        }

        try {

            User saveUser = new User();
            BeanUtils.copyProperties(user, saveUser);   // 把请求数据的属性copy给存储数据库的属性
            saveUser(saveUser);

        } catch (Exception e) {
            throw new RuntimeException("数据库异常",e);
        }

    }

    // 校验邮箱
    private void validateEmail(String emailKey,String emailCode){
        Integer code =  RedisUtils.getCacheObject(emailKey);

        if( code == null ){
            throw new ServiceException("验证码失效");
        }

        if( !emailCode.equals(code.toString())){
            throw new ServiceException("验证码错误");
        }

        RedisUtils.deleteObject(emailKey);
    }

    @Override
    public void sendEmail(String email, String type) {
        String emailPrefix = EmailCodeEnum.getValue(type);

        if (StrUtil.isBlank(emailPrefix)) {
            throw new ServiceException("不支持的邮箱验证类型");
        }

        // 设置redis key
        String key = Constants.EMAIL_CODE + emailPrefix + email;
        Long expireTime = RedisUtils.getExpireTime(key);     // 从redis拿数据，需要连接
        // 限制超过1分钟才可以继续发送邮件，判断过期时间是否大于4分钟
        if (expireTime != null && expireTime > 4 * 60) {
            throw new ServiceException("发送邮箱验证过于频繁");
        }

        Integer code = Integer.valueOf(RandomUtil.randomNumbers(6));
        log.info("本次验证的code是：{}", code);
        String context = "<b>尊敬的用户：</b><br><br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;您好，" +
        "Partner交友网提醒您本次的验证码是：<b>{}</b>，" +
        "有效期5分钟。<br><br><br><b>Partner交友网</b>";
        String html = StrUtil.format(context, code);

        // 校验邮箱是否已注册 （不需要权限验证即可发送邮箱验证码）
        User user = getOne(new QueryWrapper<User>().eq("email", email));
        if("REGISTER".equals(type)){
            if (user != null) {
                throw new ServiceException("邮箱已注册");
            }
        } else if (EmailCodeEnum.RESET_PASSWORD.equals(EmailCodeEnum.getEnum(type))) {
            if (user == null) {
                throw new ServiceException("未找到用户");
            }
        }


        ThreadUtil.execAsync(() -> {   // 多线程执行异步请求，可以防止网络阻塞
            emailUtils.sendHtml("【Partner交友网】验证提醒", html, email);
            // 用户收到验证码后在写入Redis才有意义
            RedisUtils.setCacheObject(key, code, TIME_IN_MS5, TimeUnit.MILLISECONDS);
        });


    }

    // 忘记密码时 重置密码
    @Override
    public String passwordReset(UserRequest userRequest) {
        String email = userRequest.getEmail();
        User dbUser = getOne(new UpdateWrapper<User>().eq("email", email));
        if (dbUser == null) {
            throw new ServiceException("未找到用户");
        }
        String key = Constants.EMAIL_CODE + EmailCodeEnum.RESET_PASSWORD.getValue() + email;
        validateEmail(key,userRequest.getEmailCode());
        String newPass = "123";
        dbUser.setPassword(BCrypt.hashpw(newPass));
        // 设置到数据库
        try {
            updateById(dbUser);
        } catch (Exception e) {
            throw new RuntimeException("注册失败", e);
        }
        return newPass;
    }

    // 登陆后在个人中心 修改密码
    public void passwordChange(UserRequest userRequest) {
        //  UserRequest 加 uid、newPassword 两个字段
        User dbUser = getOne(new UpdateWrapper<User>().eq("uid", userRequest.getUid()));
        if (dbUser == null) {
            throw new ServiceException("未找到用户");
        }
        // 判断原密码对不对
        boolean checkpw = BCrypt.checkpw(userRequest.getPassword(), dbUser.getPassword());
        if (!checkpw) {
            throw new ServiceException("原密码错误");
        }
        // 设置新密码
        String newPass = userRequest.getNewPassword();

        // 加密  设置到数据库
        dbUser.setPassword(BCrypt.hashpw(newPass));

        updateById(dbUser);
    }


    public User saveUser(User user) {
        User dbUser = getOne(new UpdateWrapper<User>().eq("username", user.getUsername()));
        if (dbUser != null) {
            throw new ServiceException("用户已注册");
        }
        // 设置昵称
        if (StrUtil.isBlank(user.getNamex())) {
            user.setNamex("系统用户" + RandomUtil.randomString(6));
        }

        // 设置默认密码
        if (StrUtil.isBlank(user.getPassword())) {
            user.setPassword("123");
        }

        // 加密用户密码
//        user.setPassword(SaSecureUtil.aesEncrypt(Constants.LOGIN_USER_KEY,user.getPassword()));
        user.setPassword(BCrypt.hashpw(user.getPassword()));  // BCrypt加密

        // 设置唯一标识
        user.setUid(IdUtil.fastSimpleUUID());

        try {
            save(user);
        } catch (Exception e){
            throw new RuntimeException("注册失败",e);
        }

        return user;
    }

    @Override
    public void logout(String uid) {
        // 退出登录
        StpUtil.logout(uid);
        log.info("用户{}退出成功", uid);
    }
    /**
     *  更新用户积分
     * @param score 待更新的积分
     * @param userId  用户id
     */
    public static void setScore(Integer score, Integer userId) {
        UserMapper userMapper = SpringContextUtil.getBean(UserMapper.class);
        userMapper.setScore(score, userId);
    }

}
